package com.example.memory.util;

import java.util.concurrent.atomic.AtomicReference;

public class SnowflakeIdGenerator {
    private SnowflakeIdMeta idMeta;
    private AtomicReference<Variant> variant = new AtomicReference(new Variant());

    public SnowflakeIdGenerator(SnowflakeIdMeta idMeta) {
        this.idMeta = idMeta;
    }

    public SnowflakeIdGenerator() {
        this.idMeta = new SnowflakeIdMeta(1, (byte) 3, (byte) 15, (byte) 45);
    }

    private long tillNextTimestamp(long lastTimestamp) {
        long timestamp;
        for (timestamp = System.currentTimeMillis(); timestamp < lastTimestamp; timestamp = System.currentTimeMillis()) {
        }

        return timestamp;
    }

    public Long getUId() {
        Variant oldVariant;
        Variant newVariant;
        long uid;
        do {
            oldVariant = this.variant.get();
            uid = oldVariant.lastTimestamp;
            long timestamp = this.tillNextTimestamp(uid);
            long sequence = oldVariant.sequence;
            if (uid == timestamp) {
                sequence = sequence + 1L & this.idMeta.getSequenceMask();
                if (sequence == 0L) {
                    timestamp = this.tillNextTimestamp(uid);
                }
            } else {
                sequence = 0L;
            }

            newVariant = new Variant();
            newVariant.sequence = sequence;
            newVariant.lastTimestamp = timestamp;
        } while (!this.variant.compareAndSet(oldVariant, newVariant));

        uid = this.idMeta.getMachine();
        uid |= newVariant.sequence << (int) this.idMeta.getSequenceStartPos();
        uid |= newVariant.lastTimestamp << (int) this.idMeta.getTimeStartPos();
        return uid;
    }

    public String getSuid() {
        return Long.toString(this.getUId());
    }

    private static class Variant {
        long sequence;
        long lastTimestamp;

        private Variant() {
            this.lastTimestamp = -1L;
        }
    }

    private static class SnowflakeIdMeta {
        private long machine;
        private byte machineBits;
        private byte sequenceBits;
        private byte timeSequence;

        public SnowflakeIdMeta(long machine, byte machineBits, byte sequenceBits, byte timeSequence) {
            this.machine = machine;
            this.machineBits = machineBits;
            this.sequenceBits = sequenceBits;
            this.timeSequence = timeSequence;
        }

        public long getMachine() {
            return this.machine;
        }

        public byte getMachineBits() {
            return this.machineBits;
        }

        public byte getSequenceBits() {
            return this.sequenceBits;
        }

        public byte getTimeSequence() {
            return this.timeSequence;
        }

        public long getSequenceMask() {
            return ~(-1L << this.sequenceBits);
        }

        public long getSequenceStartPos() {
            return this.machineBits;
        }

        public long getTimeStartPos() {
            return (this.machineBits + this.sequenceBits);
        }
    }
}